/*  adc-module.c
 *
 *  Author:    Michael Schwarz
 *  Copyright (C) 2011 Michael Schwarz
 *
 * ADC module for LPC313x
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#include <mach/hardware.h>
#include <mach/gpio.h>


#define ADC_AUTHOR        "Michael Schwarz"
#define ADC_DESCRIPTION   "ADC Driver for GNUBLIN"

#define ADC_INT_ENABLE  1
#define ADC_Rx_DATA     1023
#define ADC_ENABLE      (1 << 1)
#define ADC_CSCAN       (1 << 2)
#define ADC_SSCAN       (~ADC_CSCAN)
#define ADC_START       (1 << 3)
#define ADC_STOP        (~ADC_START)
#define ADC_STATUS      (1 << 4)
#define ADC_INT_STATUS  1
#define ADC_INT_CLEAR   1


// default status
#define ADC_CON_DEFAULT          0
#define ADC_CSEL_DEFAULT         0  
#define ADC_INT_ENABLE_DEFAULT   0
#define ADC_INT_STATUS_DEFAULT   0
#define ADC_INT_CLEAR_DEFAULT    0

#define ADC_CALC_CSEL(ch, res) ((res) << ((ch) * 4))
#define ADC_DATA_MASK(res) ((1 << (res)) - 1)


#define PROCFS_MAX_SIZE       2

#define PROCFS_ADC0           "adc0"
#define PROCFS_ADC1           "adc1"
#define PROCFS_ADC3           "adc3"
#define PROCFS_ADC_RES        "adc_resolution"


static char procfs_buffer[PROCFS_MAX_SIZE];
static unsigned long procfs_buffer_size = 0;

static int adc_resolution = 10; // adc resolution in bit

// prototypes
u16 adc_read(int channel, int resolution);
int adc_resolution_write(struct file* file, const char* buffer, unsigned long count, void* data);
int adc_resolution_read(char* buffer, char** buffer_location, off_t offset, int buffer_length, int* eof, void* data);
int adc0_read(char* buffer, char** buffer_location, off_t offset, int buffer_length, int* eof, void* data);
int adc1_read(char* buffer, char** buffer_location, off_t offset, int buffer_length, int* eof, void* data);
int adc3_read(char* buffer, char** buffer_location, off_t offset, int buffer_length, int* eof, void* data);
void create_procfs(char* name, void* fnc_read, void* fnc_write);


// ------------------------------------ /procfs functions ----------------------------------------

int adc_resolution_write(struct file* file, const char* buffer, unsigned long count, void* data) {
 procfs_buffer_size = (count > PROCFS_MAX_SIZE ? PROCFS_MAX_SIZE : count);
 
 if(copy_from_user(procfs_buffer, buffer, procfs_buffer_size)) {
  return -EFAULT; 
 }
 
 adc_resolution = procfs_buffer[0];
 
 return procfs_buffer_size;
}

int adc_resolution_read(char* buffer, char** buffer_location, off_t offset, int buffer_length, int* eof, void* data) {
 buffer[0] = adc_resolution;
 return 1;
}


int adc0_read(char* buffer, char** buffer_location, off_t offset, int buffer_length, int* eof, void* data) {
 u16 result = adc_read(0, adc_resolution);
 
 buffer[0] = result & 0xff;
 buffer[1] = (result >> 8) & 0xff;
 return 2;
}

int adc1_read(char* buffer, char** buffer_location, off_t offset, int buffer_length, int* eof, void* data) {
 u16 result = adc_read(1, adc_resolution);
 
 buffer[0] = result & 0xff;
 buffer[1] = (result >> 8) & 0xff;
 return 2;
}

int adc3_read(char* buffer, char** buffer_location, off_t offset, int buffer_length, int* eof, void* data) {
 u16 result = adc_read(3, adc_resolution);
 
 buffer[0] = result & 0xff;
 buffer[1] = (result >> 8) & 0xff;
 return 2;
}


void create_procfs(char* name, void* fnc_read, void* fnc_write) {
 struct proc_dir_entry *proc_file;
 proc_file = create_proc_entry(name, 0644, NULL);

 if(proc_file == NULL) {
  remove_proc_entry(name, NULL);
  printk(KERN_ALERT "Error: Could not initialize /proc/%s\n", name);
  return;// -ENOMEM;
 }

 proc_file->read_proc  = fnc_read;
 proc_file->write_proc = fnc_write;
 proc_file->mode       = S_IFREG | S_IRUGO;
 proc_file->uid        = 0;
 proc_file->gid        = 0;
 proc_file->size       = 37;
 
}

// ------------------------------------ ADC ------------------------------------

u16 adc_read(int channel, int resolution) {
 u16 result = 0xffff;
 int timeout = 0xffffff;
 
 /* wait if there is a conversion */
 if((ADC_INT_STATUS_REG & 1)) {
  while(ADC_INT_STATUS_REG & 1) {
   ADC_INT_CLEAR_REG |= (ADC_INT_CLEAR);
  }
 }

 ADC_INT_ENABLE_REG |= ADC_INT_ENABLE;
 /* wait until interrupt is cleared */
 if((ADC_INT_STATUS_REG & 1)) {
  while(ADC_INT_STATUS_REG & 1) {
   ADC_INT_CLEAR_REG |= (ADC_INT_CLEAR);
  }
 } 
 
 /* select channel and resolution */
 ADC_CSEL_REG = ADC_CALC_CSEL(channel & 3, resolution); 

 /* single conversion mode */
 ADC_CON_REG &= ADC_SSCAN;

 /* start */
 ADC_CON_REG |= ADC_START;
 
 /* wait until a conversion is done*/
 while((ADC_INT_STATUS_REG == 0) && timeout--);
 if(timeout == 0) return 0xffff;
 
 /* clear interrupt */
 ADC_INT_CLEAR_REG |= ADC_INT_CLEAR;
 /* read in the 10 bit value of the converted channel */
 switch(channel) {
   case 0:
     result = (u16)ADC_R0_REG & ADC_Rx_DATA;
     break;
   case 1:
     result = (u16)ADC_R1_REG & ADC_Rx_DATA;
     break;
   case 2:
     result = (u16)ADC_R2_REG & ADC_Rx_DATA;
     break;
   case 3:
     result = (u16)ADC_R3_REG & ADC_Rx_DATA;
     break;
 }

 /* stop the ADC */
 ADC_CON_REG &= ADC_STOP;
 
 /* wait until the interrupt status bit is cleared */
 while(ADC_INT_STATUS_REG & 1) {
  ADC_INT_CLEAR_REG |= ADC_INT_CLEAR;
 }
 
 return result;
}





int __init init_adc(void) {
 u32 dummy;
  
 printk("adc-module: init\n");
 
 /* power up ADC */
 SYS_ADC_PD = 0;
 
 /* enable clock for ADC */
 cgu_clk_en_dis(CGU_SB_ADC_CLK_ID, 1);
 cgu_clk_en_dis(CGU_SB_ADC_PCLK_ID, 1);
 
 /* reset to default */
 ADC_CON_REG = ADC_CON_DEFAULT;
 ADC_CSEL_REG = ADC_CSEL_DEFAULT;
 ADC_INT_ENABLE_REG = ADC_INT_ENABLE_DEFAULT;
 ADC_INT_CLEAR_REG = ADC_INT_CLEAR_DEFAULT;
 
 /* dummy interupt status register read */
 dummy = ADC_INT_STATUS_REG;
 while(ADC_INT_STATUS_REG & 1);

 /* enable ADC interrupt */
 ADC_INT_ENABLE_REG |= ADC_INT_ENABLE;

 /* set ADC power up */
 ADC_CON_REG |= ADC_ENABLE;
 
 create_procfs(PROCFS_ADC0, adc0_read, NULL);
 create_procfs(PROCFS_ADC1, adc1_read, NULL);
 create_procfs(PROCFS_ADC3, adc3_read, NULL);
 //create_procfs(PROCFS_ADC_RES, adc_resolution_read, adc_resolution_write);
 
 adc_resolution = 10; 
 
 return 0;
}

void __exit cleanup_adc(void) {
 printk("adc-module: cleanup\n");
 
 /* wait while there is a conversion */
 while (ADC_INT_STATUS_REG & 0x10);

 /* set ADC to default state */
 ADC_CON_REG = ADC_CON_DEFAULT;
 ADC_CSEL_REG = ADC_CSEL_DEFAULT;
 ADC_INT_ENABLE_REG = ADC_INT_ENABLE_DEFAULT;
 ADC_INT_CLEAR_REG = ADC_INT_CLEAR_DEFAULT;

 /* disable clock for ADC */
 cgu_clk_en_dis(CGU_SB_ADC_CLK_ID, 0);
 cgu_clk_en_dis(CGU_SB_ADC_PCLK_ID, 0);
 
 remove_proc_entry(PROCFS_ADC0, NULL);
 remove_proc_entry(PROCFS_ADC1, NULL);
 remove_proc_entry(PROCFS_ADC3, NULL);
 //remove_proc_entry(PROCFS_ADC_RES, NULL);
 
 /* power down ADC */
 SYS_ADC_PD = 1;
}

module_init(init_adc);
module_exit(cleanup_adc);

MODULE_LICENSE("GPL");

MODULE_AUTHOR(ADC_AUTHOR);  
MODULE_DESCRIPTION(ADC_DESCRIPTION);    
 
